﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Media;
using Bookvar.ObjectModel;
using Bookvar.Common;
using Bookvar.ObjectModel.Actions.Collaboration;
using Bookvar.ObjectModel.Collections;

namespace Bookvar.Collaboration.ServerSide
{
    /// <summary>
    /// This class manages the collaboration users on the server side.
    /// </summary>
    internal class CollaborationUsersManager : ICollaborationUsersManager
    {
		//private static readonly object lockMe = new object();
        private static readonly ColorsSequence colorsQueue  = new ColorsSequence();

        private readonly IDictionary<CollaborationUser, ICollaborationServiceCallback>
            collaborationUserCallbackDictionary;

        private readonly CollaborationUserCollection users;

        private CollaborationUser host;

        /// <summary>
        /// Initializes a new instance of the <see cref="CollaborationUsersManager"/> class.
        /// </summary>
        /// <param name="hostName">The name of the host.</param>
        public CollaborationUsersManager(string hostName)
        {
            collaborationUserCallbackDictionary = new Dictionary<CollaborationUser, ICollaborationServiceCallback>();
            users = new CollaborationUserCollection();
            
            AddHostToUsers(hostName);
        }

        /// <summary>
        /// Gets the collaboration users, including the host. This collection is intended to be readonly.
        /// </summary>
        /// <value>The collaboration users.</value>
        public CollaborationUserCollection CollaborationUsers
        {
            get { return users; }
        }

        /// <summary>
        /// Gets the host.
        /// </summary>
        /// <value>The host.</value>
        public CollaborationUser Host
        {
            get { return host; }
        }

        private void AddHostToUsers(string hostName)
        {
            host = GenerateNewUser(hostName);
            ICollaborationServiceCallback dummyHostCallback = new DummyCollaborationServiceCallback();
            AddUser(host, dummyHostCallback);
        }

        /// <summary>
        /// Adds the provided user and the callback associated with it.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// if the user id or name are already used.
        /// </exception>
        /// <param name="user">The user.</param>
        /// <param name="callback">The callback.</param>
        public void AddUser(CollaborationUser user, ICollaborationServiceCallback callback)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            if (callback == null)
            {
                throw new ArgumentNullException("callback");
            }

            ValidateNewUser(user);
            collaborationUserCallbackDictionary.Add(user, callback);
            users.Add( user );
            OnUserJoined(new CollaborationUserEventArgs(user));
        }

        /// <summary>
        /// Determines whether the specified user name is free to use.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>
        /// 	<c>true</c> if the specified name is free; otherwise, <c>false</c>.
        /// </returns>
        public bool IsUserNameFree(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            bool result = CheckIfNameIsFree(name);
            return result;
        }

        /// <summary>
        /// Determines whether the specified user id is free to use.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns>
        /// 	<c>true</c> if user id is free to use; otherwise, <c>false</c>.
        /// </returns>
        public bool IsUserIdFree(Guid id)
        {
            bool result = CheckIfIdIsFree(id);
            return result;
        }

        /// <summary>
        /// Generates the new user.
        /// </summary>
        /// <param name="name">Name of the user.</param>
        /// <returns></returns>
        public CollaborationUser GenerateNewUser(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            bool nameIsFree = CheckIfNameIsFree(name);

            if (nameIsFree == false)
            {
                throw new InvalidOperationException("User name is already in use.");
            }

            Guid id = GenerateID();
            Color color = colorsQueue.DequeueColor();

            CollaborationUser user = new CollaborationUser(id, name, color);
            return user;
        }

        /// <summary>
        /// Informs the collaboration users that an user joined.
        /// </summary>
        /// <param name="user">The user.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void InformUserJoined(CollaborationUser user)
        {
            List<CollaborationUser> toRemove = new List<CollaborationUser>();

            foreach (var pair in collaborationUserCallbackDictionary)
            {
                if (pair.Key.ID != user.ID)
                {
                    try
                    {
                        pair.Value.BeginUserEnter(user, null, null);
                    }
                    catch
                    {
                        toRemove.Add(pair.Key);
                    }
                }
            }

            foreach (CollaborationUser u in toRemove)
            {
                UserLeaves(u);
            }
        }

        /// <summary>
        /// Informs the collaboration users to process the action.
        /// </summary>
        /// <param name="action">The action.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void InformProcessAction(ActionBase action)
        {
            List<CollaborationUser> toRemove = new List<CollaborationUser>();

            foreach (
                KeyValuePair<CollaborationUser, ICollaborationServiceCallback> pair in
                    collaborationUserCallbackDictionary)
            {
                if (pair.Key.ID != action.PublisherGuid)
                {
                    try
                    {
                        pair.Value.BeginDoAction(action, null, null);
                    }
                    catch
                    {
                        toRemove.Add(pair.Key);
                    }
                }
            }

            foreach (CollaborationUser user in toRemove)
            {
                UserLeaves(user);
            }
        }

        /// <summary>
        /// Informs the collaboration users that the server is closed.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void InformServerClosed()
        {
            foreach (KeyValuePair<CollaborationUser, ICollaborationServiceCallback> pair in
                collaborationUserCallbackDictionary)
            {
                try
                {
                    pair.Value.BeginServerClosed(Host, null, null);
                }
                catch
                {
                    
                }
            }
        }

        public void UserLeaves(CollaborationUser client)
        {
            RemoveUser(client);
            InformUserLeft(client);
        }

        public event EventHandler<CollaborationUserEventArgs> UserJoined;

        public event EventHandler<CollaborationUserEventArgs> UserLeft;

        private void OnUserJoined(CollaborationUserEventArgs e)
        {
            EventHandler<CollaborationUserEventArgs> toInvoke = UserJoined;
           
            if(toInvoke != null)
            {
                toInvoke(this, e);
            }
        }

        private void OnUserLeft(CollaborationUserEventArgs e)
        {
            EventHandler<CollaborationUserEventArgs> toInvoke = UserLeft;

            if(toInvoke != null)
            {
                toInvoke(this, e);
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void InformUserLeft(CollaborationUser client)
        {
            List<CollaborationUser> toRemove = new List<CollaborationUser>();

            foreach (
                KeyValuePair<CollaborationUser, ICollaborationServiceCallback> pair in
                    collaborationUserCallbackDictionary)
            {
                if (pair.Key.ID == client.ID)
                {
                    try
                    {
                        pair.Value.BeginUserLeave(client, null, null);
                    }
                    catch
                    {
                        toRemove.Add(pair.Key);
                    }
                }
            }

            foreach (CollaborationUser user in toRemove)
            {
                UserLeaves(user);
            }
        }

        private void RemoveUser(CollaborationUser user)
        {
            colorsQueue.EnqueueColor(user.UserColor);
            collaborationUserCallbackDictionary.Remove(user);
            OnUserLeft(new CollaborationUserEventArgs(user));
        }

        private Guid GenerateID()
        {
            while (true)
            {
                Guid id = Guid.NewGuid();
                bool idIsFree = CheckIfIdIsFree(id);
                if (idIsFree)
                {
                    return id;
                }
            }
        }

        private void ValidateNewUser(CollaborationUser user)
        {
            bool idIsFree = CheckIfIdIsFree(user.ID);
            bool nameIsFree = CheckIfNameIsFree(user.Name);

            if (idIsFree == false && nameIsFree == false)
            {
                throw new InvalidOperationException("User name and id are already in use.");
            }
            
            if (idIsFree == false)
            {
                throw new InvalidOperationException("User id is already in use.");
            }

            if (nameIsFree == false)
            {
                throw new InvalidOperationException("User name is already in use");
            }
        }

        private bool CheckIfIdIsFree(Guid id)
        {
            return !users.Contains( id );
        }

        private bool CheckIfNameIsFree(string name)
        {
            foreach (var user in users)
            {
                if ( user.Name.Trim().Equals( name.Trim(), StringComparison.OrdinalIgnoreCase ) )
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// This class is used as a dummy callback for the host. 
        /// </summary>
        private class DummyCollaborationServiceCallback : ICollaborationServiceCallback
        {
            void ICollaborationServiceCallback.UserEnter(CollaborationUser user)
            {
            }

            void ICollaborationServiceCallback.UserLeave(CollaborationUser user)
            {
            }

            public IAsyncResult BeginUserLeave(CollaborationUser user, AsyncCallback c, object s)
            {
                return null;
            }

            public void EndUserLeave(IAsyncResult result)
            {
            }

            void ICollaborationServiceCallback.DoAction(ActionBase action)
            {
            }

            IAsyncResult ICollaborationServiceCallback.BeginUserEnter(
                CollaborationUser user, AsyncCallback callback, object asyncState)
            {
                return null;
            }

            void ICollaborationServiceCallback.EndUserEnter(IAsyncResult result)
            {
            }

            IAsyncResult ICollaborationServiceCallback.BeginDoAction(
                ActionBase action, AsyncCallback callback, object asyncState)
            {
                return null;
            }

            void ICollaborationServiceCallback.EndDoAction(IAsyncResult result)
            {
            }

            public void ServerClosed(CollaborationUser host)
            {
            }

            public IAsyncResult BeginServerClosed(CollaborationUser host, AsyncCallback callback, object asyncState)
            {
                return null;
            }

            public void EndServerClosed(IAsyncResult result)
            {
                
            }
        }
    }
}
